// include files

#include "data-structures.h"

// VLOG constant
#define THIS_MODULE VLM_data_structures


// static function definitions

/**
 * dequeues a node from the pending queue, caller does not block
 * if the queue is empty
 */
static DCQueueNode*
DCQueue_dequeue(DCQueue *q);

/**
 * enqueues a node into the pending queue
 */
static void
DCQueue_enqueue(DCQueue *q, DCQueueNode *newNode);

/**
 * enqueues a node into the pending queue
 */
static void
DCQueue_enqueue(DCQueue *q, DCQueueNode *newNode);

/**
 * dequeues a node from the pending queue, but caller blocks until
 * the queue contains elements
 */
static DCQueueNode* DCQueue_waitAndDequeue(DCQueue *q);


/**
 *
 * This function implements the initialization logic
 * for the PendingQueue
 * @param q The PendingQueue to initialize
 * @return void
 */
void DCQueue_init(DCQueue *q, int isBlocking) {
    assert (q != NULL);

    q->count = 0;
    q->isBlocking = 0;

    if (isBlocking) {
        q->isBlocking = isBlocking;
        pthread_cond_init(&(q->hasElementsCV), NULL);
    }

    pthread_mutex_init(&(q->pendingQueueMutex), NULL);

    q->head = NULL;
    q->tail = NULL;
}

/**
 * This function frees the underlyinge
 * and destroys the underlying mutex
 * @param q The pending queue to destroy
 */
void DCQueue_destroy(DCQueue *q) {
    DCQueueNode *cur;
    DCQueueNode *next;

    assert (q != NULL);


    for (cur = q->head; cur != NULL; cur = next) {
        next = cur->next;
        free(cur);
    }

    q->count = 0;

    if (q->isBlocking) {
        pthread_cond_destroy(&(q->hasElementsCV));

    }
    pthread_mutex_destroy(&(q->pendingQueueMutex));
}



/**
 * Dequeues the first item (found at the head)
 * Caller is responsible for deallocating
 *
 * TODO: make this just return the data and free the node
 *
 * @return NULL if queue is empty, otherwise a DCQueueNode
 *              that the caller manages
 */
DCQueueNode* DCQueue_dequeue(DCQueue *q) {
    DCQueueNode *dequeuedNode = NULL;

    assert (q != NULL);

    // begin critical section
    pthread_mutex_lock(&(q->pendingQueueMutex));

    if (q->head != NULL && q->count > 0) {
        dequeuedNode = q->head;

        // advance the head to the next in line
        q->head = dequeuedNode->next;

        if (q->head == NULL) {
            q->tail = NULL;
        }

        // isolate the dequeued node
        dequeuedNode->next = NULL;

        // decrement the number of items in the queue
        q->count--;
    }

    pthread_mutex_unlock(&(q->pendingQueueMutex));
    // end critical section

    return dequeuedNode;
}

/**
 * dequeues just the data and deallocates the
 * underlying node
 */
void* DCQueue_dequeueData(DCQueue *q) {

    DCQueueNode *dequeuedNode = NULL;
    void *data = NULL;

    assert(q!=NULL);

    dequeuedNode = DCQueue_dequeue(q);

    if (dequeuedNode != NULL) {

        data = dequeuedNode->data;
        free(dequeuedNode);
    }

    return data;
}

/**
 *
 * enqueues data into the caller specified queue
 *
 * allocates a node on behalf of the caller
 *
 */
void DCQueue_enqueueData(DCQueue *q, void *data) {

    DCQueueNode *node = (DCQueueNode*) malloc(sizeof(struct DCQueueNode));

    assert(node != NULL);
    node->data = data;

    DCQueue_enqueue(q,node);
}

/**
 *
 * Dequeues items for the queue.  Caller blocks until there are items enqueued
 * @param q - The queue to dequeue from
 * @returns A queue node
 *
 */
DCQueueNode* DCQueue_waitAndDequeue(DCQueue *q) {
    DCQueueNode *dequeuedNode = NULL;

    assert (q != NULL);
    assert (q->isBlocking);

    pthread_mutex_lock(&(q->pendingQueueMutex));

    while (q->count < 1) {
        pthread_cond_wait(&(q->hasElementsCV), &(q->pendingQueueMutex));
    }


    if (q->head != NULL && q->count > 0) {
        dequeuedNode = q->head;

        // advance the head to the next in line
        q->head = dequeuedNode->next;

        if (q->head == NULL) {
            q->tail = NULL;
        }

        // isolate the dequeued node
        dequeuedNode->next = NULL;

        // decrement the number of items in the queue
        q->count--;
    }

    pthread_mutex_unlock(&(q->pendingQueueMutex));

    return dequeuedNode;
}


/**
 * will block the caller until data exists in the underlying queue to
 * be dequeued.
 */
void* DCQueue_waitAndDequeueData(DCQueue *q) {

    DCQueueNode *node = NULL;
    void *data = NULL;

    assert(q!=NULL);

    node = DCQueue_waitAndDequeue(q);

    if (node != NULL) {
        data = node->data;
        free(node);
    }

    return data;

}


/**
 *
 * This function adds a new node to the tail of the queue
 * @param q The pending queue to append to
 * @param newNode The new pending queue node to append.
 */
void DCQueue_enqueue(DCQueue *q, DCQueueNode *newNode) {

    assert (q != NULL);
    assert (newNode != NULL);

    // begin critical section
    pthread_mutex_lock(&(q->pendingQueueMutex));

    newNode->next = NULL;

    if (q->count++) {

        q->tail->next = newNode;

    } else {

        // first item
        q->head = newNode;
    }

    q->tail = newNode;

    if (q->isBlocking) {
        pthread_cond_broadcast(&(q->hasElementsCV));
    }

    // end critical section
    pthread_mutex_unlock(&(q->pendingQueueMutex));
}

